<?php
declare (strict_types=1);

namespace app\middleware;

use app\common\service\lib\Jwt;
use app\common\service\lib\Sign;
use app\Request;
use think\Exception;
use think\facade\Cache;
use think\Response;

class Throttle
{
    protected $version = '1.0';//版本号
    protected $time;//时间
    protected $limit;//访问次数

    protected $isOverstep = false;//是否超出请求频率限制

    protected $cache_key;//缓存key

    protected $option = [
        'prefix' => '',//key前缀
        'key' => true,//key值
        'visit_rate' => '',//访问频率
        'visit_method' => ['GET', 'POST'],//请求类型
        'visit_fail_text' => '',//访问授权提示文本
        'key_params' => []//指定参数 作为key的组合值，
    ];

    /**
     * 初始化
     * Throttle constructor.
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->option = array_merge($this->option, config('throttle'));
    }

    /**
     * 处理访问频率
     */
    private function visitRate()
    {
        list($limit, $time) = explode('/', $this->option['visit_rate']);
        $this->limit = $limit;
        if (is_numeric($time)) {
            $this->time = (int)$time;
        } else {
            switch ($time) {
                case 's':
                    //秒
                    $this->time = 1;
                    break;
                case 'm':
                    //分钟
                    $this->time = 60;
                    break;
                case 'h':
                    //小时
                    $this->time = 3600;
                    break;
                case 'd':
                    //天
                    $this->time = 24 * 3600;
                    break;
                default:
                    break;
            }
        }
    }

    public function setCacheKey($request)
    {
        $ip = $request->ip();

        if ($this->option['key'] === true) {
            //1 参数可以直接拿到
            $this->cache_key = $this->option['prefix'] . $ip;
        } else {
            //2 参数需要转换 比如userId需要通过token转化 或者 已加密的数据需要解密后才能拿到
            $key_val = explode('/', $this->option['key']);
            $cache_key = [];

            if (in_array('__app__', $key_val)) array_push($cache_key, app('http')->getName());
            if (in_array('__controller__', $key_val)) array_push($cache_key, $request->controller());
            if (in_array('__action__', $key_val)) array_push($cache_key, $request->action());
            if (in_array('__params__', $key_val)) array_push($cache_key, http_build_query($request->param()));

            if ($this->option['key_params']) array_push($cache_key, http_build_query($request->param($this->option['key_params'])));

            if (in_array('__ip__', $key_val)) array_push($cache_key, $ip);

            $this->cache_key = $this->option['prefix'] . join('_', $cache_key);
        }

        $this->cache_key = md5($this->cache_key);
    }

    /**
     * 处理自定义参数
     * @param Request $request
     * 缓存key值，key:__app__/__controller__/__action__/__url__/__params__/__data__/__ip__
     * 访问频率visit_rate:100/m
     */
    public function setConfig($request)
    {
        //处理访问频率
        $this->visitRate();

        //设置缓存
        $this->setCacheKey($request);
    }

    /**
     * 处理请求
     * @param $request
     * @param \Closure $next
     * @param array $params
     * @return mixed
     * @throws Exception
     */
    public function handle($request, \Closure $next, array $params = [])
    {
        $web = request()->header('Origin');
        //跨域请求设置
        header("Access-Control-Request-Method:GET,POST");
        header("Access-Control-Allow-Credentials:true");
        header("Access-Control-Allow-Origin:".$web);
        header("Access-Control-Allow-Headers:token,Content-Type, Authorization, Accept, Range, Origin,Token,Lang,lang");
        if($request->isOptions()){
            exit;
        }

        //处理额外的配置参数
        $this->option = $params ? array_merge($this->option, $params) : $this->option;

        //visit_rate 为null时 表示不限制
        if (!$this->option['visit_rate']) return $next($request);

        $this->setConfig($request);

        $redis = Cache::store('redis');
        $redis->select(1);
        $check = $redis->get($this->cache_key);

        if ($check) {
            if ((int)$check >= $this->limit) {
                $this->isOverstep = true;
            } else {
                $redis->inc($this->cache_key);
            }
        } else {
            $redis->set($this->cache_key, $redis->inc($this->cache_key), $this->time);
        }

        /**
         * 是否超出请求频率限制，
         * 这里根据需要去处理是否抛出异常
         */
        $request->isOverstep = $this->isOverstep;
        if (!empty($this->isOverstep)) {
            $visit_fail_text = str_replace('__WAIT__', $this->time, $this->option['visit_fail_text']);
            throw new Exception($visit_fail_text);
        }

//        $sign = $request->param('sign');
        $token = $request->header('authorization');
        $data = Jwt::checkToken($token);

        if ($data['code'] == 2) {
            return Response::create(['code' => 400, 'data' => [], 'msg' => $data['message']], 'json', 400);
        }
        if (isset($data['data']->id)) {
            $request->uid = decrypt($data['data']->id);
        }

        return $next($request);
    }
}
